/*
 * GSeasonServiceAdapter.java
 * Created on 10 août 2008
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2008, Jens and Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.seasoncycle;


import org.springframework.transaction.annotation.Transactional;

import com.hockeo.vhbase.data.dao.IGDao;
import com.hockeo.vhbase.data.dao.season.GMoveday;
import com.hockeo.vhbase.data.dao.season.GSeason;
import com.hockeo.vhbase.error.GDataException;
import com.hockeo.vhbase.error.GException;

/**
 * Adapter class around the season service ({@link GSeasonService}). This class must be
 * used for controlling the season life cycle. It knows about the right methods to call
 * depending on the current season situation.
 *
 * @version $Id$
 * @author jjanke
 */
public class GSeasonServiceAdapter
{
  private IGDao     d_dao;
  private GSeasonService d_seasonService;

  public void setDao( final IGDao dao )
  {
    d_dao = dao;
  }

  public void setSeasonService( final GSeasonService service )
  {
    d_seasonService = service;
  }

  /**
   * Resets the currently active season. Be very careful when using this method since
   * everything since the beginning of the season (incl. all played matches) will be
   * deleted or reset.
   *
   * @throws GDataException if there is a problem resetting the season
   */
  @Transactional( readOnly = false, rollbackFor = { GException.class } )
  public void resetCurrentSeason() throws GDataException
  {
    d_seasonService.resetSeason( true );
  }

  /**
   * Resets the entire game database to its initial state. Be very careful when using this
   * method since everything will be deleted or reset and this command is not reversible
   * unless you have a physical database backup.
   *
   * @throws GDataException if there is a problem resetting the whole database
   */
  @Transactional( readOnly = false, rollbackFor = { GException.class } )
  public void resetAll() throws GException
  {
    d_seasonService.resetAll();
  }

  /**
   * Plays the entire season that is currently active from its current position until the
   * end of the season (stops just before creating a new season). Be aware that human
   * players all risk to be fired since they may get more than three NMRs...
   *
   * @throws GDataException
   */
  public void playWholeSeason() throws GException
  {
    GCurrentSeasonState seasonState = getCurrentSeasonState();

    while ( seasonState != GCurrentSeasonState.SEASON_ENDED )
    {
      next( seasonState );
      seasonState = getCurrentSeasonState();
    }
  }

  /**
   * Advances the game to the next step.
   *
   * @throws GException
   */
  @Transactional( readOnly = false, rollbackFor = { GException.class } )
  public void next() throws GException
  {
    next( getCurrentSeasonState() );
  }

  /**
   * Advances the game to the next step.
   *
   * @param seasonState the current season state
   * @throws GException
   */
  private void next( GCurrentSeasonState seasonState ) throws GException
  {
    switch ( seasonState )
    {
      case NO_ACTIVE_SEASON:
        d_seasonService.createNewSeason();
        break;

      case BEFORE_SEASON_START:
        d_seasonService.startSeason();
        break;

      case SEASON_STARTED:
        d_seasonService.prepareNextContestRounds();
        break;

      case MOVEDAY:
        d_seasonService.executeMoveday();
        d_seasonService.prepareNextContestRounds();
        break;

      case BEFORE_SEASON_END:
        d_seasonService.endSeason();
        break;

      case SEASON_ENDED:
        d_seasonService.createNewSeason();
        break;

      default:
        throw new IllegalStateException( "Unexpected season situation: " + seasonState.name() );
    }
  }

  /**
   * Reverses the last action executed by calling the {@link #next()} method.
   *
   * @throws GException
   */
  @Transactional( readOnly = false, rollbackFor = { GException.class } )
  public void reverseLastAction() throws GException
  {
    GCurrentSeasonState curSituation = getCurrentSeasonState();

    switch ( curSituation )
    {
      case NO_ACTIVE_SEASON:
        throw new GDataException( "The current season situation " + curSituation.name()
            + " cannot be reversed automatically and requires manual intervention." );

      case BEFORE_SEASON_START:
        d_seasonService.undoCreateNewSeason();
        break;

      case SEASON_STARTED:
        d_seasonService.resetSeason( false );
        break;

      case MOVEDAY:
        GMoveday movedayLastPrepared = d_dao.findLastPreparedMoveday();

        if ( !movedayLastPrepared.isExecuted() )
          d_seasonService.undoPrepareNextContestRounds();

        d_seasonService.undoExecuteMoveday();
        break;

      case BEFORE_SEASON_END:
        d_seasonService.undoExecuteMoveday();
        break;

      case SEASON_ENDED:
        d_seasonService.resetEndOfSeason();
        break;

      default:
        throw new IllegalStateException( "Unexpected season situation: " + curSituation.name() );
    }
  }

  /**
   * Determines the current situation of the season.
   *
   * @throws GDataException
   */
  public GCurrentSeasonState getCurrentSeasonState() throws GDataException
  {
    GSeason season;

    try
    {
      season = d_dao.findCurrentSeason();
    }
    catch ( GDataException ex )
    {
      return GCurrentSeasonState.NO_ACTIVE_SEASON;
    }

    if ( !season.isStarted() )
      return GCurrentSeasonState.BEFORE_SEASON_START;

    GMoveday moveday = d_dao.findCurrentMoveday();
    if ( moveday != null )
    {
      if ( !moveday.isPrepared() )
        return GCurrentSeasonState.SEASON_STARTED;

      return GCurrentSeasonState.MOVEDAY;
    }

    if ( !season.isFinished() )
      return GCurrentSeasonState.BEFORE_SEASON_END;

    return GCurrentSeasonState.SEASON_ENDED;
  }
}
